Skip to content

Conversation

@anjuthomas
Copy link
Contributor

No description provided.

@anjuthomas anjuthomas requested a review from haamel May 18, 2018 05:08
const assert = require('chai').assert;

/**
* @fileoverview This class keeps information that a client needs to be
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that the @fileoverview is needed for this file, as it only contains a single class. Instead I would suggest to make this the class comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

/**
* serviceContext
* @class
* @constructor
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The annotations @Class and @constructor are redundant here.

Copy link
Contributor Author

@anjuthomas anjuthomas May 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have removed the @constructor to avoid redundancy

*/
class ServiceContext {
/**
* This class keeps information that a client needs to be able to talk
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the description, as it's the same as above and only keep the @param annotations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

} else if (attr === 'requests_dir') {
this.request_dir = this.config[attr] || defaultVal;
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the semicolon.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

} else if (attr === 'provider_info') {
this.provider_info = this.config[attr] || defaultVal;
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the semicolon.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

try {
this.callback = this.config['callback'];
} catch (err) {
this.callback = {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at EOL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -0,0 +1,177 @@
const State = require('./state.js').State;

This comment was marked as resolved.

This comment was marked as resolved.

const State = require('./state.js').State;
const crypto = require('crypto');
const KeyJar = require('../nodeOIDCMsg/src/oicMsg/keystore/KeyJar').KeyJar;
const assert = require('chai').assert;
Copy link
Collaborator

@haamel haamel May 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused. Remove?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Collaborator

@haamel haamel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments for now, and some general questions.

Also, could you please spend some time on the tests? I haven't made any comments on them yet, but I find some tests, e.g. 'set and get client secret' or 'set and get client id' quite pointless, as these set a member variable directly and immediately afterwards assert that this member was set to the same value. Some more meaningful unit test would be great.

Thanks a lot.

let serviceContext = ['client_id', 'issuer', 'client_secret', 'base_url', 'requests_dir'];
let defaultVal = '';

if (params){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{style} Add a whitespace before '{'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

const KeyJar = require('../nodeOIDCMsg/src/oicMsg/keystore/KeyJar').KeyJar;

/**
* ServiceContext
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this line as it does not give additional value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

* to a server. Some of this information comes from configuration and some
* from dynamic provider info discovery or client registration.
* But information is also picked up during the conversation with a server.
* @class
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this annotation as the 'class' keyword is already used below (line 12) when defining the ServiceContext.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


/**
* Need to generate a redirect_uri path that is unique for a OP/RP combo
This is to counter the mix-up attack.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{style} Please put a '*' in front of this comment line to make it clear that it belongs to the block comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


setClientSecret(val) {
if (!val) {
this.client_secret;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you wanted to have the default value set when there is no 'val'. So, this should be:
this.client_secret = '';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

keyjar = keyjar || null;
config = config || null;
this.keyjar = keyjar || new KeyJar();
this.providerInfo = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that several member variables are defined with similar naming, e.g.:

  • providerInfo vs. provider_info,
  • baseUrl vs. base_url,
  • requestDir vs. requests_dir,
  • cId vs. client_id,
  • cSecret vs. clientSecret vs. client_secret,
  • clientPreferences vs. client_prefs

Is this intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The providerInfo and provider_info is intentional. One refers to the class object and the other refers to config attr. All other names are unintentional and I have fixed it. Good catch ;)

}
}

for (let i = 0; i < serviceContext.length; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you are doing this through a loop? I find it quite hard to read and would rather prefer something like:

this.client_id = this.config['client_id'] || defaultVal;
this.issuer = this.config['issuer'] || defaultVal;
...

This would also have the benefit that you do not have to initialize the members with their default value, as done above. If that is the intention, due to the naming inconsistencies it is somewhat hard to tell ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Removed the for loop.

@@ -0,0 +1,161 @@
const crypto = require('crypto');
const KeyJar = require('../nodeOIDCMsg/src/oicMsg/keystore/KeyJar').KeyJar;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module location will change once you're done with the nodeOIDCMsg repo, right?
If so, could you already require the correct and to be expected location of the module here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would the module location change?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this wasn't too clear. What I meant is whether nodeOIDCMsg becomes a dependency of the nodeOIDCService at some point that is listed in the dependency section of the package.json file? If that's the case the path in this in this require statement would slightly change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my todo list

*/
constructor(keyjar, config, params) {
this.clientSecret = [this.getClientSecret, this.setClientSecret];
keyjar = keyjar || null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed as it is pointless to initialize keyjar when you initialize this.keyjar properly just 2 lines later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

constructor(keyjar, config, params) {
this.clientSecret = [this.getClientSecret, this.setClientSecret];
keyjar = keyjar || null;
config = config || null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove. this.config will be initialized properly in line 26 regardless of this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

return this;
}

getclient_secret() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this change from getClientSecret to getclient_secret intentional?

return this.client_secret;
}

setclient_secret(val) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above. I think it wasn't your intention to also change this function name?

* corresponding file on the local filesystem would be 'jwks_uri'.
* Relative to the directory from which the RP instance is run.
*
* @param {*} webName
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be of type 'string' rather than '*'?

*
* As an example if the base_url is 'https://example.com' and a jwks_uri
* is 'https://example.com/jwks_uri.json' then the filename of the
* corresponding file on the local filesystem would be 'jwks_uri'.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment is not correct here. To match with the given example it should state:
"corresponding file on the local filesystem would be 'jwks_uri.json"

if (name.startsWith('/')) {
return name.substring(1, name.length);
} else {
let splitName = name.split('/');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add more unit tests for this function to cover all the various cases to determine the local filename.

Also cover the case, as mentioned above, when the webName doesn't start with the base_url.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my TODO list

this.redirectUris = [null];
}

try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be simplified to this.callback = this.config['callback'] || {};.

}

if (config && Object.keys(config).indexOf('keydefs') !== -1) {
this.keyjar = this.buildKeyJar(config['keydefs'], this.keyjar)[1];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.buildKeyJar is not defined in this class.

if (this.keyjar == null) {
this.keyjar = new KeyJar();
}
this.keyjar.addSymmetric('', val.toString());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a unit test that verifies that a symmetric key is added to the keyjar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my TODO list

}
}

describe('', function() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of the tests within this describe? It's testing functionality that is not provided by the ServiceContext class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these methods are only for ServiceContext purposes, i thought there is no better place to put these tests..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just about the tests, but mainly about the functionality they are testing. signEncAlgs and verifyAlgSupport are defined in this test class either.

What is the general plan for these methods? If they are used by other classes at some point they should certainly not live here in this test class file. They should rather live in the ServiceContext class or maybe some helper or some other class depending on its further usage.

It probably makes sense to send me more PRs in parallel, so that I get a better understanding of the system and its structure. Would that be possible?

Copy link
Contributor Author

@anjuthomas anjuthomas Jun 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added methods to ServiceContext. Also, I have sent another PR

} else if (attr === 'base_url') {
assert.deepEqual(ci.base_url, config[attr]);
} else if (attr === 'requests_dir') {
assert.deepEqual(ci.base_url, config[attr]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not use this kind of "for loop combined with if else if construct" ever again in unit tests. First of all it's really hard to read and furthermore it's error prone.

Like in this the tests pass even though they shouldn't as you deepEqual ci.base_url against config['requests_dir'] which should return false based on the config used and defined above. The root cause here is that this code path is never executed, because the for statement in line 269 is wrong. The for statement uses the keys array (Object.keys(config)) rather than the length of the keys array (Object.keys(config).length) to determine whether should run or not, and apparently is does not run. ;)

Therefore always assert things that should be there, and if things shouldn't be there assert that. This will make your tests more clear, shorter and bullet proof. Or we'll end up having tests for tests ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed :)

let ci;
beforeEach(function() {
config = {
'client_id': 'client_id',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to not use the key name as its value. This could easily lead to confusion and unnoticed failures.


it('client info init', function() {
assert.isNotNull(ci);
if (Object.keys(config).indexOf('client_id') > -1) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again: assert things that should be there, and if things shouldn't be there assert that!

First of all, this if (...) {...} else if {... block will only test one of the five key/value pairs. That's probably not what you meant to test.

If you wanted to test whether the ServiceContext.config is equal to the provided one in the constructor, then you could simply do the following instead of the if block:

assert.deepEqual(ci.config, config);

If you also wanted to check whether the other members, like client_id, are initialized correctly as well you should do the following in addition:

assert.strictEqual(ci.client_id, 'client_id');
assert.strictEqual(ci.issuer, 'issuer');
assert.strictEqual(ci.client_secret, 'client_secret');
assert.strictEqual(ci.base_url, 'https://example.com');
assert.strictEqual(ci.request_dir, 'requests');

Also, client_secret is quite special as it should be accessed through getClientSecret or setClientSecret rather than ci.client_secret, right?
Therefore you could also use the following:

assert.strictEqual(ci.getClientSecret(), 'client_secret');

Nevertheless, setClientSecret and getClientSecret should have their own tests.

Overall this will make your tests more clear, shorter and bullet proof.


this.client_id = this.config['client_id'] || defaultVal;
this.issuer = this.config['issuer'] || defaultVal;
this.client_secret = this.config['client_secret'] || defaultVal;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this line.

* @param {string} typ: 'id_token', 'userinfo' or 'request_object'
*/
signEncAlgs(typ) {
let serviceContext = this;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this and replace all occurrences of serviceContext. in this function with this..

for (let i = 0; i < Object.keys(ATTRMAP[typ]).length; i++) {
let key = Object.keys(ATTRMAP[typ])[i];
let val = ATTRMAP[typ][key];
if (serviceContext.registrationResponse && serviceContext.registrationResponse[val]){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{style} Missing whitespace between ) and {.

* - id_token
* - request_object
* - token_endpoint_auth
* @param {string} typ Type of alg
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this parameter into type. It's more intuitive.

*/
verifyAlgSupport(alg, usage, typ) {
let serviceContext = this;
let supported = serviceContext.providerInfo[usage + '_' + typ + '_values_supported'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if providerInfo is not set or the constructed key is not present in providerInfo?

this.client_id = this.config['client_id'] || defaultVal;
this.issuer = this.config['issuer'] || defaultVal;
this.client_secret = this.config['client_secret'] || defaultVal;
this.setClientSecret(this.client_secret);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to

  this.setClientSecret(this.config['client_secret']);

});

it('client info init', function() {
assert.deepEqual(ci.clientSecret, 'supersecret');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intention of this test?
It should literally always pass, unless you decide using the new ES6 set/get class syntax. Then it might fail depending on your implementation.

Also it is not clientSecret, but client_secret.

});

it('client info init clientId', function() {
assert.deepEqual(ci.clientId, 'myself');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intention of this test?
It should literally always pass, unless you decide using the new ES6 set/get class syntax. Then it might fail depending on your implementation.

Also it is not clientId, but client_id.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants